﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity.Validation;

namespace Data.implement
{
    internal class DataAccessBase<TD> : IDataAccessBase where TD : DbContext, new()
    {
        protected TD context;

        private static readonly log4net.ILog log =
            log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private static object lockObj = new object();


        internal DataAccessBase()
        {
            //lock (lockObj)
            {
                context = new TD();
            }
        }

        ~DataAccessBase()
        {
            context.Dispose();
        }

        public void Dispose()
        {
            context.Dispose();
        }

        public DbSet<T> GetTable<T>() where T : class
        {
            Type type = context.GetType();
            Type tableType = typeof(T);
            DbSet<T> respon;
            var property = type.GetProperty(tableType.Name);
            //lock (lockObj)
            {
                respon = (DbSet<T>)Convert.ChangeType(property.GetValue(context, null), typeof(DbSet<T>));
            }
            log.DebugFormat("Get Table {0} .", tableType.Name);
            return respon;
        }

        public T GetRecordById<T>(params object[] propertyObjects) where T : class
        {
            var table = GetTable<T>();
            T result;
            //lock (lockObj)
            {
                result = table.Find(propertyObjects);
            }
            return result;
        }

        public List<T> GetAllRecord<T>() where T : class
        {
            var table = GetTable<T>();
            List<T> query;
            //lock (lockObj)
            {
                query = (from s in table select s).ToList();
            }
            return query;
        }

        public List<T> GetRecordsByProperty<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var table = GetTable<T>();
            List<T> result;
            //lock (lockObj)
            {
                result = table.Where(predicate).ToList();
            }
            return result;
        }

        public List<T> GetRecordsByProperty<T,TF>(Expression<Func<T, TF>> orderField,int limit, bool desc = false) where T : class
        {
            var table = GetTable<T>();
            List<T> result;
            //lock (lockObj)
            if(limit > 0)
            {
                if (desc)
                {
                    result = table.OrderByDescending(orderField).Take(limit).ToList();
                }
                else
                {
                    result = table.OrderBy(orderField).Take(limit).ToList();
                }
            }else
            {
                if (desc)
                {
                    result = table.OrderByDescending(orderField).ToList();
                }
                else
                {
                    result = table.OrderBy(orderField).ToList();
                }
            }

            return result;
        }

        public List<T> GetRecordsByProperty<T>(Expression<Func<T, bool>> predicate, int limit) where T : class
        {
            var table = GetTable<T>();
            List<T> result;
            if (limit > 0)
                result = table.Where(predicate).Take(limit).ToList();
            else
            {
                result = table.Where(predicate).ToList();
            }
            return result;
        }

        /// <summary>
        /// get records with paging
        /// </summary>
        /// <typeparam name="T">the type of items of the entities list</typeparam>
        /// <typeparam name="TF">the type of order field by descending</typeparam>
        /// <param name="predicate"></param>
        /// <param name="orderField"></param>
        /// <param name="pageIndex">page index started with 0. numbers that less than 0 will be treated as 0</param>
        /// <param name="pageSize">the number of return results. return all if less than or equal 0</param>
        /// <returns></returns>
        public List<T> GetRecordsByProperty<T, TF>(Expression<Func<T, bool>> predicate, Expression<Func<T, TF>> orderField, int pageIndex, int pageSize) where T : class
        {
            var table = GetTable<T>();
            List<T> result;
            if (pageIndex < 0)
            {
                pageIndex = 0;
            }
            if (pageSize > 0)
                result = table.Where(predicate).OrderByDescending(orderField).Skip(pageIndex * pageSize).Take(pageSize).ToList();
            else
            {
                result = table.Where(predicate).ToList();
            }
            return result;
        }

        public int CountRecordsByProperty<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var table = GetTable<T>();
            return table.Count(predicate);
        }

        public T GetRecordByProperty<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var table = GetTable<T>();
            T result;
            //lock (lockObj)
            {
                result = table.FirstOrDefault(predicate);
            }
            return result;
        }

        public T GetLastRecordByProperty<T, TF>(Expression<Func<T, bool>> predicate, Expression<Func<T, TF>> orderField) where T : class
        {
            var table = GetTable<T>();
            T result;
            //lock (lockObj)
            {
                result = table.Where(predicate).OrderByDescending(orderField).FirstOrDefault();
            }
            return result;
        }

        public List<T> GetLastRecordsByProperty<T, TF>(Expression<Func<T, bool>> predicate, Expression<Func<T, TF>> orderField) where T : class
        {
            var table = GetTable<T>();
            List<T> result;
            //lock (lockObj)
            {
                result = table.Where(predicate).OrderByDescending(orderField).ToList();
            }
            return result;
        }

        public bool CheckRecordByProperty<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var table = GetTable<T>();
            bool result;
            //lock (lockObj)
            {
                result = table.Any(predicate);
            }
            return result;
        }

        public T InsertRecord<T>(T insertData) where T : class
        {
            try
            {
                T respon;
                //lock (lockObj)
                {
                    var table = GetTable<T>();
                    log.DebugFormat("Before inserting data on table {0}.", typeof(T).ToString());
                    respon = table.Add(insertData);
                    context.SaveChanges();
                }
                //SaveChanges();
                //context.Entry(respon).State = EntityState.Detached;
                return respon;
            }
            catch (DbEntityValidationException ex)
            {
                log.Error("context Entity validation exception", ex);
                if (ex.EntityValidationErrors != null)
                {
                    foreach (var entErr in ex.EntityValidationErrors)
                    {
                        foreach (var valErr in entErr.ValidationErrors)
                        {
                            log.ErrorFormat("Validation error for entry {0} on field {2} is: {1}",
                                entErr.Entry.Entity.ToString(), valErr.ErrorMessage, valErr.PropertyName);
                        }
                    }
                }
                throw ex;
            }
        }

        public List<T> InsertRecords<T>(IList<T> insertData) where T : class
        {
            var table = GetTable<T>();
            var respon = new List<T>();
            foreach (var data in insertData)
            {
                respon.Add(table.Add(data));
            }
            SaveChanges();
            return respon;
        }

        public bool DeleteRecord<T>(T deleteData) where T : class
        {
            var table = GetTable<T>();
            table.Remove(deleteData);
            SaveChanges();
            return true;
        }

        public bool DeleteRecords<T>(IList<T> deleteDatas) where T : class
        {
            var table = GetTable<T>();
            foreach (var deleteData in deleteDatas)
            {
                table.Remove(deleteData);
            }
            SaveChanges();
            return true;
        }

        public T UpdateRecord<T>(T changedData) where T : class
        {
            SaveChanges();
            return changedData;
        }

        public IList<T> UpdateRecords<T>(IList<T> changedDatas) where T : class
        {
            SaveChanges();
            return changedDatas;
        }

        private void SaveChanges()
        {
            //lock (lockObj)
            {
                context.SaveChanges();
            }
        }

        protected void ResetContext()
        {
            context.Dispose();
            context = new TD();
        }
    }
}
